function VicAfterEffectApply (args)
{
var source = args.source_unit;
// best guess of source unit if info not available i.e this event was triggered of scripting
if (source == null && vicLastActedUnit != null)
source = vicLastActedUnit;

var target = args.target_unit;
var targetTile = args.target_tile;
var effect = args.effect;

vicApplyMultipleEffectsOnFlag(effect, target, targetTile, "effects_on_apply"); 

if (source != null) 
{
vicApplyMultipleEffectsOnFlag(effect, source, null, "source_effects_on_apply"); // applies effects to the source only, not the source tile
vicApplyMultipleEffectsOnFlag(effect, null, source.Tile, "source_tile_effects_on_apply");
vicRemoveMultipleEffectsOnFlag(effect, source, null, "remove_source_effects_on_apply"); 
vicRemoveMultipleEffectsOnFlag(effect, null, source.Tile, "remove_source_tile_effects_on_apply"); 
}

vicRemoveMultipleEffectsOnFlag(effect, target, targetTile, "remove_effects_on_apply"); // implements the remove_effects_on_apply flag, which when on application of a unit effect, can remove other effects to that unit
vicReplaceEffectsOnFlag(effect, target, targetTile, "replace_effects");
vicReplaceTileOccupantsEffectsOnFlag(effect, targetTile, "replace_occupants_effects");
}

function vicAfterEffectRemove(args)
{
var source = args.source_unit;
// best guess of source unit if info not available i.e this event was triggered of scripting
if (source == null && vicLastActedUnit != null)
source = vicLastActedUnit;

var target = args.target_unit;
var targetTile = args.target_tile;
var effect = args.effect;

vicApplyMultipleEffectsOnFlag(effect, target, targetTile, "effects_on_remove"); // implements the effects_on_remove flag, which applies a list of effects on the removal of an effect
vicRemoveMultipleEffectsOnFlag(effect, target, targetTile, "remove_effects_on_remove"); // implements the remove_effects_on_remove flag, which when on application of a unit effect, can remove other effects to that unit

if (source != null) 
{
vicApplyMultipleEffectsOnFlag(effect, source, null, "source_effects_on_remove"); // applies effects to the source only, not the source tile
vicApplyMultipleEffectsOnFlag(effect, null, source.Tile, "source_tile_effects_on_remove");
vicRemoveMultipleEffectsOnFlag(effect, source, null, "remove_source_effects_on_remove"); 
vicRemoveMultipleEffectsOnFlag(effect, null, source.Tile, "remove_source_tile_effects_on_remove"); 
}
}

function vicAfterEffectFizzle(args)
{
var target = args.target_unit;
var targetTile = args.target_tile;
var effect = args.effect;

vicApplyMultipleEffectsOnFlag(effect, target, targetTile, "effects_on_fizzle"); // implements the effects_on_fizzle flag, which applies a list of effects on the fizzling of an effect
vicRemoveMultipleEffectsOnFlag(effect, target, targetTile, "remove_effects_on_fizzle"); // implements the remove_effects_on_fizzle flag, which when on application of a unit effect, can remove other effects to that unit
}

// checks for the existence of a specified flag on an effect. If present, the line is then split into lists of effects, which is then passed to the ApplyMultipleEffects function. Though the first parameter is named effect, it'll work just as well on skill or item objects
// affectsWholeTile is an optional boolean that determines whether the target is treated as an entire tile or just a single unit. If an entire tile, then tile effects are applied, as well as unit effects to all occupants. This overrides the built-in logic that uses the target of the skill/item/effect to determine if it should work on the entire tile
function vicApplyMultipleEffectsOnFlag(effect, unit, targetTile, flag, affectsWholeTile)
{
if (flag == null) return;
var line = effect.ScriptFlags.get(flag);
if (line == null) return;
var effects = line.split(", ");

// regardless of whether the effect script is used in has a tile/unit target, any tile effects specified will be applied to the tile
vicApplyMultipleTileEffects(effects, targetTile)

if (affectsWholeTile == null) affectsWholeTile = (vicGetTarget(effect) == "tile");

if (affectsWholeTile)
{
vicApplyEffectsToTileOccupants(targetTile, effects);
}
else
{
vicApplyMultipleEffects(effects, unit);
}
}

// applies the unit effects from an array of strings of effect names to a unit
function vicApplyMultipleEffects(effects, unit)
{
if (unit == null) return;

for (var i=0; i<effects.length; i++)
{
vicApplyEffect(unit, effects[i]);
}
}

// applies the tile effects from an array of strings of effect names to destination
function vicApplyMultipleTileEffects(effects, tile)
{
if (tile == null) return;

for (var i=0; i<effects.length; i++)
{
vicApplyEffect(tile, effects[i]);
}
}

// checks for the existence of a specified flag on an effect. If present, the line is then split into lists of effects, which is then passed to the RemoveMultipleEffects function
function vicRemoveMultipleEffectsOnFlag(effect, unit, targetTile, flag)
{
if (flag == null) return;
var line = effect.ScriptFlags.get(flag);
if (line == null) return;
var effects = line.split(", ");

var isTileEffect = (vicGetTarget(effect) == "tile");

// regardless of whether the effect script is used in is a tile/unit effects, effect removal is done for the target unit for unit effects, the target tile if tile effects are specified and all occupants of a tile for unit effects if the target is a tile
vicRemoveMultipleTileEffects(effects, targetTile);
vicRemoveMultipleEffects(effects, unit);
if (isTileEffect) vicRemoveEffectsFromTileOccupants(targetTile, effects);
}

// removes a list of unit effects from a given unit and an array of strings of effect names 
function vicRemoveMultipleEffects(effects, unit)
{
if (unit == null) return;
for (var i=0; i<effects.length; i++)
{
var effect = shared.GetEffectObjectByName(effects[i]);
// if this is indeed a unit effect, check if this unit is actually affected by it before attempting a removal
// isAffectedBy requires an array, doesn't work with a string
if (effect != null && vicGetTarget(effect) == "unit" && vicIsAffectedBy(unit, effects.slice(i, i+1)))
{
shared.RemoveEffect(effects[i], unit);
}
}
}

// removes a list of tile effects from a given tile and an array of strings of effect names 
function vicRemoveMultipleTileEffects(effects, tile)
{
if (tile == null) return;
for (var i=0; i<effects.length; i++)
{
var effect = shared.GetEffectObjectByName(effects[i]);
// if this is indeed a tile effect, check if this tile is actually affected by it before attempting a removal
// isAffectedBy requires an array, doesn't work with a string
if (effect != null && vicGetTarget(effect) == "tile" && vicIsAffectedBy(tile, effects.slice(i, i+1)))
{
shared.RemoveEffect(effects[i], null, tile);
}
}
}

// checks for the presence of a script flag of the form replace_effects effect1|replacement1, effect2|replacement2 ... and if found, perform effect substitution
// obj is either an effect, skill or item object
// target or targetTile is the unit or tile that the replacements should be made for
function vicReplaceEffectsOnFlag(obj, target, targetTile, flag)
{
if (flag == null) return;
var line = obj.ScriptFlags.get(flag);
if (line == null) return;
var substitutions = line.split(/, |\|/);
var replaceLimitLine = obj.ScriptFlags.get("replace_effects_limit");
var limit = null; // positive integer limit

if (replaceLimitLine != null)
{
limit = shared.Calculate(replaceLimitLine);
}

var hasTile = (vicGetTarget(obj) == "tile"); // whether effect/item/skill applies to single unit or a tile

if (hasTile)
{
vicReplaceEffects(targetTile, hasTile, substitutions, limit); // for replacing a tile effect on that tile to another
}
else
{
vicReplaceEffects(target, hasTile, substitutions, limit); // for replacing a tile effect on that tile to another
}
}

// logic for effect substitution, replacing an effect with another if the former is found, given a unit or tile object, a boolean indication whether obj is a tile and a string array of substitutions. An optional integer can also be used to limit the replacements applied
// note this only does replacement for a unit effect on a unit for another, unit effect, or a tile effect on a tile for another tile effect
// precondition: the array size must be divisible by 2, and is in the order effect0, replacement0, effect1, replacement1 ... 
function vicReplaceEffects(obj, isTile, substitutions, limit)
{
if (substitutions.length%2 != 0) return; // can't do anything if substitutions isn't a length divisible by 2
var replacements = 0; // count of replacements made

for (var i=0; (limit == null || replacements < limit) && i<substitutions.length; i+=2)
{
// if the left side of the pair is none, check that this tile/unit is indeed not affected by anything before the pair in the replace_effects line to handle replace_effects none|new effect
var isNone =  (substitutions[i] == "none");

// if this is index 0, then there is nothing to the left for the target to be affected by. Otherwise, it needs to be checked
if (isNone && (i == 0 || vicIsAffectedBy(obj, substitutions.slice(0, i)) == false))
{
vicReplaceEffectsHandler(obj, isTile, "none", substitutions[i+1])
replacements++;
}

// handle regular replacements
for (var j=0; isNone == false && j<obj.Effects.length; j++)
{
var curEffect = obj.Effects[j].IndexedName;

// do the replacement if the oldEffect of this pair is found. Note that even if oldEffect isn't the same as newEffect, some processing needs to be done to see if user wants the effect reapplied
if (substitutions[i] == curEffect)
{
vicReplaceEffectsHandler(obj, isTile, curEffect, substitutions[i+1], substitutions.slice(0, i+1));
replacements++;
}
}
}
}

// checks for the presence of a script flag of the form replace_occupants_effects effect1|replacement1, effect2|replacement2 ... and if found, perform unit effect substitution for all occupants of a tile
// obj is either an effect, skill or item object
// targetTile is the tile that the occupants replacements should be made for
function vicReplaceTileOccupantsEffectsOnFlag(obj, targetTile, flag)
{
if (flag == null) return;
var line = obj.ScriptFlags.get(flag);
if (line == null) return;
var hasTile = (vicGetTarget(obj) == "tile"); // whether effect/item/skill applies to single unit or a tile

// this should only work if the skill/item/effect applies to a tile
if (hasTile == false) return;
var substitutions = line.split(/, |\|/);
var replaceLimitLine = obj.ScriptFlags.get("replace_occupants_effects_limit");
var limit = null; // positive integer limit

if (replaceLimitLine != null)
{
limit = shared.Calculate(replaceLimitLine);
}
vicReplaceEffectsForTileOccupants(targetTile, substitutions, limit); // for replacement of any unit effects for all units on the tile.
}

// handles the actual effect replacement; this function prevents lots of code duplication in the above functions
// target is either a tile or unit object. OldEffect is an effect name string and newEffect is an unparsed effect name string (which may have duration information). Makes no sense to have duration information on oldEffect
// effectList is a string array of effects to be removed in the event where the old effect is to be replaced by the none keyword. It is optional otherwise
function vicReplaceEffectsHandler(target, isTile, oldEffect, newEffect, effectList)
{
var newEffectName = vicParseEffect(newEffect).name;
if (oldEffect == "none" && newEffectName == "none") return; // invalid condition!
if (oldEffect == newEffectName) return; // the reapply prefix for a new effect means that the strings aren't actually equal, so nothing to replace

// dealing with none|reapply effectName. If the newEffect is prefixed with the word reapply followed by a space,, and if it is the same as oldEffect, that means that it should be reapplied. Otherwise, it is left untouched
if (oldEffect != "none")
{
var startIndex = newEffectName.search(/^reapply /); // if this returns 0 then the match is found

if (startIndex == 0)
newEffectName = newEffectName.replace("reapply ", ""); // remove the leading reapply

if (oldEffect == newEffectName)
{
newEffect = newEffect.replace("reapply ", ""); // // remove the leading reapply from newEffect, so that it can be parsed into applyEffect
vicApplyEffect(target, newEffect);
return;
}
}

// none|newEffect case
if (oldEffect == "none")
{
vicApplyEffect(target, newEffect);
return;
} 

// oldEffect|none case
if (newEffectName == "none")
{
if (isTile)
{
vicRemoveMultipleTileEffects(effectList, target);
}
else
{
vicRemoveMultipleEffects(effectList, target);
}
return;
}

// oldEffect|newEffect
if (oldEffect != "none" && newEffectName != "none")
{
if (isTile)
{
shared.RemoveEffect(oldEffect, null, target);
}
else
{
shared.RemoveEffect(oldEffect, target);
}
vicApplyEffect(target, newEffect);
}
}

// checks if a unit or tile object is affected by any of the effects in a string array of unparsed effect names, returning a boolean
// used primarily for none keyword processing in replace_effects
function vicIsAffectedBy(obj, effectList)
{
var effects = obj.Effects;

for (var i=0; i<effects.length; i++)
{
for (var j=0; j<effectList.length; j++)
{
if (effects[i].IndexedName == vicParseEffect(effectList[j]).name) return true;
}
}

return false;
}

// handles effect replacement for all occupants on the tile at once. Needed for skill/effect/items with tiles as targets but with replace_effect meant for units on that tile, not the tile itself
// limit controls how many effect replacements are made on a per-unit basis, not the number of units on the tile this works for
function vicReplaceEffectsForTileOccupants(tile, substitutions, limit)
{
var units = tile.Units;
var structures = tile.Structures;

for (var i=0; i<units.length; i++)
{
vicReplaceEffects(units[i], false, substitutions, limit);
}

for (var i=0; i<structures.length; i++)
{
vicReplaceEffects(structures[i], false, substitutions, limit);
}
}

// applies the unit effects to all units and structures in a tile, given a tile object and an array of effect name strings 
function vicApplyEffectsToTileOccupants(tile, effects)
{
if (tile == null) return;
var units = tile.Units;
var structures = tile.Structures;

for (var i=0; i<effects.length; i++)
{
var effect = shared.GetEffectObjectByName(vicParseEffect(effects[i]).name);

// only apply the effect to occupants if this is indeed a unit effect. Doing the initial effect target check here rather than just using applyEffect for greater efficiency
if (vicGetTarget(effect) == "unit")
{
for (var j=0; j<units.length; j++)
{
vicApplyEffect(units[j], effects[i]);
}

for (var j=0; j<structures.length; j++)
{
vicApplyEffect(structures[j], effects[i]);
}
}
}
}

// removes the unit effects from all units and structures in a tile, given a tile object and an array of effect name strings 
function vicRemoveEffectsFromTileOccupants(tile, effects)
{
if (tile == null) return;
var units = tile.Units;
var structures = tile.Structures;

for (var i=0; i<effects.length; i++)
{
var effect = shared.GetEffectObjectByName(effects[i]);
// only remove the effect from occupants if this is indeed a unit effect
if (effect != null && vicGetTarget(effect) == "unit")
{
for (var j=0; j<units.length; j++)
{
shared.RemoveEffect(effects[i], units[j]);
}

for (var j=0; j<structures.length; j++)
{
shared.RemoveEffect(effects[i], structures[j]);
}
}
}
}

/*
processes an effect name string to see if optional effect duration mods were specified. Returns an object with the properties name and durationMod
the following duration modifiers are supported:
effectName[integer] - overrides the effect's duration with that integer
effectName[+integer], effectName[-integer]: modifies the effect's base duration by the amount specified
effectName[*r] - modifies the effect duration by a multiplier, which can be any nonnegative real number
*/

function vicParseEffect(effect)
{
var startIndex = effect.search(/\[(|\+|\-|\*)[.0-9]+\]$/); // -1 if not found, or the start index of the modifier
if (startIndex == -1) 
return {"name": effect, "durationMod": null};

var name = effect.slice(0, startIndex);
var mod = effect.slice(startIndex+1, effect.length-1); // startIndex+1 to exclude starting [, length - 1 to exclude the closing ]
return {"name": name, "durationMod": mod}
}

// applies the effect name to the target, which may be a unit or a tile object. Effect name should be unparsed so that this function can consolodate all handling of duration modifiers. In the future, this will also support immune_effects handling
// the effect and target types are also checked to ensure that there is type matching
function vicApplyEffect(target, effect)
{
var curRound = (shared.Map.CurrentRound == 0) ? 1 : shared.Map.CurrentRound; // the game starts at round 0 when afterMapLoad is triggered, but that causes all round effects to expire 1 round sooner than expected, so must set to 1 for our expiry calcs
var parsedEffect = vicParseEffect(effect);
var effectObject = shared.GetEffectObjectByName(parsedEffect.name);
var targetType = vicGetType(target); // "unit" or "tile"
if (vicGetTarget(effectObject) != targetType) return; // must be both the same
var duration =   parseInt(effectObject.AllFlags.get("duration"), 10); // explicitly specify base 10 in case
var originalRoundExpires = shared.Map.CurrentRound + duration; // this is what the game will calculate without these scripts active. It is checked with the calculated value - if it differs, that means that modification is needed

if (parsedEffect.durationMod != null)
{
switch(parsedEffect.durationMod.charAt(0)) // whether the first character is a plus sign, minus, multiplication or a digit
{
case "+": case "-": duration += parseInt(parsedEffect.durationMod, 10); break; // if the modifier is a negative, then addition of a negative number results in a subtraction, which is right
case "*": 
var multiplier = parsedEffect.durationMod.replace("*", ""); // remove the initial * as it breaks parseFloat
duration *= parseFloat(multiplier); 
duration = Math.round(duration); 
break; // round durations must be integers
default: duration = parseInt(parsedEffect.durationMod);
}
}
var roundExpires = curRound + duration; // is correct for both normal applications and case with modifiers

switch (vicGetType(target))
{
case "unit": shared.ApplyEffect(parsedEffect.name, target); break;
case "tile": shared.ApplyEffect(parsedEffect.name, null, target); break;
default: throw "vicApplyEffect(): could not determine target type for effect application";
}

// now search for the target's list of effects for the effect we just applied if any round adjustment is needed
// deliberately don't throw any exceptions if not found, that probably just means they used some other script together with this that removed the effect we just applied. In that case, we can't do a duration mod though
for (var i=0; roundExpires != originalRoundExpires && i<target.Effects.length; i++)
{
var curEffect = target.Effects[i];
if (curEffect.IndexedName == parsedEffect.name) curEffect.RoundExpires = roundExpires;
}
}